﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.TestManagement.Client;
using System.Diagnostics;
using Microsoft.TeamFoundation;
using System.Drawing;
using WorkItemVisualization.Colors;

namespace WorkItemVisualization
{
    public class ProcessFullAnalysis
    {
        #region private members
        private Dictionary<int, WorkItemStub> _workItemStubs;  //Stores the distinct list of all work items to be written to the dgml
        private WorkItemStore _workItemStore;
        private VersionControlServer _vcs;
        private ITestManagementService _testManagementService;
        private ITestManagementTeamProject _testManagementTeamProject;

        private List<string> _groupByDistinctValues = new List<string>();   //Stores a distinct list of values for which we are grouping (if we are)
        private Dictionary<string, FileChangeInfo> _allFiles = new Dictionary<string, FileChangeInfo>();//Stores a distinct list of files
        private List<TempLinkType2> _links;         //Stores all link types

        private string _projectName;                //The name of the team project
        private string _outputFile;                 //The DGML file to write to        

        private string _groupByFieldReferenceName = null;    // The field reference name (if any) for which we are doing a grouping
        private string _groupByFieldName;           // the field name (if any) that we are grouping by. This is the label that will appear on the group

        private bool _dependencyAnalysis = false;   //Are we doing a dependency analysis?
        private bool _hideLinks = false;            //If we are doing a dependency analysis, should we hide the intervening nodes and links
        private bool _full;                         //Did the user select full processing
        private List<string> _allNodeTypes;         //Stores a distinct list of all node types for use in styling the nodes

        private Dictionary<string, TempLinkType> _selectedLinks; //This is the list of links that the user selected on the UI. Indexed by the LinkType 
        private WorkItemStub _tempStub;             //Used for dependency analysis to hold the stub which contains the changeset(s)

        private Dictionary<int, object> _visitedChangeSetsForWorkItem; // list to guarantee that we only process the changesets for a given WI once
        private Dictionary<string, object> _visitedServerPath;         // list to guarantee that we only process a filepath once
        private Dictionary<int, TestPlanInfo> _testPlans = new Dictionary<int, TestPlanInfo>(); //Stores a distinct list of test plans

        /// <summary>
        /// Stores whether MTM hyperlinks or TWA hyperlinks will be used
        /// Note that TWA hyperlinks are always used for changesets
        /// MTM hyperlinks are always used for test results, test plans and test runs
        /// So this only affects work items right now
        /// </summary>
        private bool _useMtmLinks = true;
        #endregion

        public ProcessFullAnalysis(WorkItemStore wis, VersionControlServer vcs,
            string projectName, string outputFile, bool useMtmLinks)
        {
            _workItemStore = wis;
            _vcs = vcs;
            _testManagementService = vcs.TeamProjectCollection.GetService<ITestManagementService>();
            _testManagementTeamProject = _testManagementService.GetTeamProject(projectName);
            _projectName = projectName;
            _outputFile = outputFile;
            _useMtmLinks = useMtmLinks;
        }

        #region Process Work Items and Changesets
        /// <summary>
        /// Method for processing work items down to the changesets that are related to them
        /// </summary>
        /// <param name="wi">Work Item to process</param>
        /// <param name="outputFile">File to write the dgml to</param>
        /// <param name="vcs">Version Control Server which contains the changesets</param>
        public void ProcessWorkItemRelationships()
        {
            _full = true;

            //Pull every work item for the project
            Query q = new Query(_workItemStore, string.Format("SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], "
                + "[System.State] FROM WorkItems WHERE [System.TeamProject] = '{0}'", _projectName));
            WorkItemCollection wic = q.RunQuery();

            WorkItem[] workItems = new WorkItem[wic.Count];

            for (int i = 0; i < wic.Count; i++)
                workItems[i] = wic[i];

            Process(workItems);
        }

        public void ProcessWorkItemRelationships(WorkItem[] wi, string groupByFieldReferenceName, bool dependencyAnalysis, List<TempLinkType> selectedLinks, bool hideLinks)
        {
            _full = false;
            _groupByFieldReferenceName = groupByFieldReferenceName;

            if (String.IsNullOrEmpty(_groupByFieldReferenceName) == false)
            {
                if (wi[0].Store.FieldDefinitions[_groupByFieldReferenceName] == null)
                {
                    _groupByFieldReferenceName = null;
                }
                else
                {
                    _groupByFieldName = wi[0].Store.FieldDefinitions[_groupByFieldReferenceName].Name;
                }
            }

            _dependencyAnalysis = dependencyAnalysis;
            _selectedLinks = GetSelectedLinksFromUI(selectedLinks);
            _hideLinks = hideLinks;

            Process(wi);
        }

        /// <summary>
        /// Gets an indexed list (by the LinkType name) list of selected link types
        /// </summary>
        /// <param name="selectedLinks"></param>
        /// <returns></returns>
        private Dictionary<string, TempLinkType> GetSelectedLinksFromUI(List<TempLinkType> selectedLinks)
        {
            Dictionary<string, TempLinkType> links;

            links = new Dictionary<string, TempLinkType>();

            foreach (TempLinkType linkType in selectedLinks)
            {
                links.Add(linkType.LinkType, linkType);
            }

            return links;
        }

        private void Process(WorkItem[] workItems)
        {
            _workItemStubs = new Dictionary<int, WorkItemStub>();
            _allNodeTypes = new List<string>();

            _visitedChangeSetsForWorkItem = new Dictionary<int, object>();
            _visitedServerPath = new Dictionary<string, object>();

            //Get the full list of link types for assigning category relationships
            _links = LoadLinkTypes(_projectName);

            //Process all work items
            for (int i = 0; i < workItems.Length; i++)
            {
                ProcessWorkItemCS(workItems[i]);
            }

            //The colors for all other node types (i.e. not work item types)
            _allNodeTypes.Add("Changeset");
            _allNodeTypes.Add("Iteration");
            _allNodeTypes.Add("File");
            _allNodeTypes.Add("Test Plan");
            _allNodeTypes.Add("Test Run");
            _allNodeTypes.Add("Test Result");

            //Process the changesets so they are also related to the actual root work item
            if (_dependencyAnalysis)
            {
                //Only process work items that have changesets
                for (int i = 0; i < _workItemStubs.Count; i++)
                {
                    if (_workItemStubs[i].Changesets.Count > 0)
                    {
                        _tempStub = _workItemStubs[i];
                        GetWorkItemParent(_tempStub);

                    }
                }
            }

            //Output the results
            WriteChangesetInfo(_outputFile, _projectName);
        }

        /// <summary>
        /// Finds the link type based on an end name
        /// </summary>
        /// <param name="value">The end to use in the search (such as Parent)</param>
        /// <returns>The name of the relationship (such as Parent/Child)</returns>
        private TempLinkType2 GetRelationship(string value)
        {
            return _links.Find(stub => { return stub.Reverse == value || stub.Forward == value; });

















        }

        /// <summary>
        /// Write the work item and changeset information to a dgml file
        /// </summary>
        /// <param name="outputFile">The file to write the dgml to</param>
        /// <param name="project">The name of the team project</param>
        /// <param name="useMtm">Indicates whether to use MTM hyperlinks (true) or TWA hyperlinks (false)</param>
        private void WriteChangesetInfo(string outputFile, string project)
        {
            IColorConfiguration colorConfiguration;

            //Create a new DGML document
            XmlTextWriter xtw = XmlHelper.CreateHeader(outputFile);

            #region Write Nodes
            //Add the nodes section
            xtw.WriteStartElement("Nodes");

            //These two arrays are used to store the single category for both files and changesets
            string[] cs = new string[] { "Has Work Items/Has Changesets" };
            string[] file = new string[] { "In Changeset/Has Files", "Associated Work Items/Has Files" };

            // add the group by values if we are grouping by
            if (String.IsNullOrEmpty(_groupByFieldReferenceName) == false)
            {
                for (int i = 0; i < _groupByDistinctValues.Count; i++)
                {
                    XmlHelper.WriteXMLNode(true, true, xtw, _groupByDistinctValues[i], _groupByDistinctValues[i], _groupByFieldName, "Expanded");
                }
            }

            //Add the individual work item nodes
            foreach (WorkItemStub workItemStub in _workItemStubs.Values)
            {
                //Stores the unique list of categories associated with this node
                List<string> categories = new List<string>();

                for (int l = 0; l < workItemStub.Related.Count; l++)
                {
                    //Check to see if the category has been added to the array and if not add it
                    if (!categories.Contains(workItemStub.Related[l].Relationship.ToString()))
                        categories.Add(workItemStub.Related[l].Relationship.ToString());
                }

                //At this point we have all the categories, write them
                XmlHelper.WriteXMLNode(true, true, xtw, workItemStub.ID.ToString(), workItemStub.Title,
                    workItemStub.WorkItemTypeName, categories.ToArray(), "WorkItem", workItemStub.ID.ToString());

                if (workItemStub.Changesets != null)
                {
                    //Add the changesets
                    for (int j = 0; j < workItemStub.Changesets.Count; j++)
                    {
                        string csID = "CS" + workItemStub.Changesets[j].ID.ToString(); //Create the changeset ID

                        if (!_dependencyAnalysis)
                        {
                            //Only add the changeset as a group if this is not a dependency analysis graph
                            XmlHelper.WriteXMLNode(true, true, xtw, csID, csID, "Changeset", "Expanded", cs, "Changeset", workItemStub.Changesets[j].ID.ToString());

                            //Add all of the files related to each changeset
                            for (int k = 0; k < workItemStub.Changesets[j].Files.Count; k++)
                            {
                                string fileId = workItemStub.Changesets[j].Files[k].FileName + ";" + csID;
                                XmlHelper.WriteXMLNode(true, true, xtw, workItemStub.Changesets[j].Files[k].FullPath + ";" + csID, fileId, "File");
                            }
                        }
                        else
                        {
                            XmlHelper.WriteXMLNode(true, true, xtw, csID, csID, "Changeset", "Changeset", workItemStub.Changesets[j].ID.ToString());
                        }
                    }
                }

                //add the test plan and test results nodes
                if (_testPlans != null)
                {
                    //Add the test plan
                    foreach (TestPlanInfo testPlanInfo in _testPlans.Values)
                    {
                        //The ID for the purposes of the DGML is "TP" + the test plan ID
                        XmlHelper.WriteXMLNode(true, true, xtw, "TP" + testPlanInfo.TestPlanID,
                            "Test Plan: " + testPlanInfo.TestPlanName, "Test Plan", "Collapsed");

                        //Add the test runs
                        for (int k = 0; k < testPlanInfo.TestRuns.Count; k++)
                        {
                            XmlHelper.WriteXMLNode(true, true, xtw, "TestRun" + testPlanInfo.TestRuns[k].ID.ToString(),
                                "Test Run " + testPlanInfo.TestRuns[k].ID.ToString(), "Test Run", "Expanded");
                        }
                    }

                    //Add the test results
                    for (int j = 0; j < workItemStub.TestResults.Count; j++)
                    {
                        //The ID for the purposes of the DGML is "TR" + the test result ID
                        string outcome = string.Format("{0}\n{1}", "Test Case: " + workItemStub.TestResults[j].TestCaseID, Enum.GetName(typeof(TestOutcome), workItemStub.TestResults[j].Outcome));
                        string trID = "TR" + workItemStub.TestResults[j].TestRunID.ToString() + workItemStub.TestResults[j].TestResultID.ToString() + workItemStub.TestResults[j].TestCaseID.ToString();
                        XmlHelper.WriteXMLNode(true, true, xtw, trID, outcome, "Test Result", "TestOutcome", Enum.GetName(typeof(TestOutcome), workItemStub.TestResults[j].Outcome));
                    }
                }
            }

            //We only add a discrete list of files if we are running a dependency analysis - otherwise all files
            //are added as part of their changesets above and are broken out into distinct files
            if (_dependencyAnalysis)
            {
                foreach (string fileName in _allFiles.Keys)
                {
                    XmlHelper.WriteXMLNode(true, true, xtw, fileName, fileName, "File", file);
                }
            }

            //Close the Nodes section
            xtw.WriteEndElement();

            #endregion

            #region Write Links
            //Add the links section
            xtw.WriteStartElement("Links");

            //Loop through the unique list of work items
            foreach (WorkItemStub workItemStub in _workItemStubs.Values)
            {
                if (String.IsNullOrEmpty(_groupByFieldReferenceName) == false)
                {
                    XmlHelper.WriteXMLLink(xtw, workItemStub.GroupyByFieldValue, workItemStub.ID.ToString(), "Contains", string.Empty);
                }

                //Add each related item to the DGML
                for (int j = 0; j < workItemStub.Related.Count; j++)
                {
                    XmlHelper.WriteXMLLink(xtw, workItemStub.ID.ToString(), workItemStub.Related[j].ID.ToString(), workItemStub.Related[j].Relationship.ToString(), string.Empty);

                    if (String.IsNullOrEmpty(_groupByFieldReferenceName) == false)
                    {
                        XmlHelper.WriteXMLLink(xtw, workItemStub.GroupyByFieldValue, workItemStub.Related[j].ID.ToString(), "Contains", string.Empty);
                    }
                }

                //Add the changesets and files to the DGML
                if (workItemStub.Changesets != null)
                {
                    for (int j = 0; j < workItemStub.Changesets.Count; j++)
                    {
                        //for each changeset, get the ID and add a link from the work item to the changeset
                        string csID = "CS" + workItemStub.Changesets[j].ID.ToString();
                        XmlHelper.WriteXMLLink(xtw, workItemStub.ID.ToString(), csID, "Has Work Items/Has Changesets", string.Empty);

                        for (int k = 0; k < workItemStub.Changesets[j].Files.Count; k++)
                        {
                            if (!_dependencyAnalysis)
                            {
                                //Add a link from the changeset to the file
                                XmlHelper.WriteXMLLink(xtw, csID, workItemStub.Changesets[j].Files[k].FullPath + ";" + csID, "In Changeset/Has Files", string.Empty);
                            }
                            else
                            {
                                XmlHelper.WriteXMLLink(xtw, workItemStub.ID.ToString(), workItemStub.Changesets[j].Files[k].FileName, "Associated Work Items/Has Files", string.Empty);
                                XmlHelper.WriteXMLLink(xtw, csID, workItemStub.Changesets[j].Files[k].FileName, "In Changeset/Has Files", string.Empty);
                            }
                        }   //End of file loop
                    }   //End of changeset loop
                }   //End of if statement

                //Add the test plans
                foreach (TestPlanInfo tesPlan in _testPlans.Values)
                {
                    for (int n = 0; n < tesPlan.TestRuns.Count; n++)
                    {
                        XmlHelper.WriteXMLLink(xtw, "TP" + tesPlan.TestPlanID.ToString(), "TestRun" + tesPlan.TestRuns[n].ID.ToString(), "Contains", string.Empty);
                    }
                }

                //Add the test results
                if (workItemStub.TestResults != null)
                {
                    for (int j = 0; j < workItemStub.TestResults.Count; j++)
                    {
                        string trID = "TR" + workItemStub.TestResults[j].TestRunID.ToString() + workItemStub.TestResults[j].TestResultID.ToString() + workItemStub.TestResults[j].TestCaseID.ToString();
                        XmlHelper.WriteXMLLink(xtw, workItemStub.TestResults[j].TestCaseID.ToString(), trID);
                        XmlHelper.WriteXMLLink(xtw, "TestRun" + workItemStub.TestResults[j].TestRunID.ToString(), trID, "Contains", string.Empty);
                    }
                }
            }

            //Close the links section
            xtw.WriteEndElement();

            #endregion

            #region Write Categories

            xtw.WriteStartElement("Categories");
            for (int i = 0; i < _links.Count; i++)
            {
                if (_links[i].Forward != "Related")
                    XmlHelper.WriteNavigationCategory(xtw, _links[i].ToString(), _links[i].Forward, _links[i].Reverse, "True");
                else
                    XmlHelper.WriteNavigationCategory(xtw, _links[i].ToString(), _links[i].Forward, _links[i].Reverse, "False");
            }
            xtw.WriteEndElement();
            #endregion

            #region Write Properties

            //Write a property node and custom properties for hyperlink usage
            string server = UrlHelper.GetBaseMtmUrl(_workItemStore);
            string mtmUrl = server + string.Format("/p:{0}/testing/", project);

            xtw.WriteStartElement("Properties");
            XmlHelper.WriteXMLProperty(xtw, "TestOutcome", "Test Outcome", "System.String");
            if (_useMtmLinks)
                XmlHelper.WriteXMLReference(xtw, "WorkItem", "True", mtmUrl + "/testcase/open?id={0}");
            else
                XmlHelper.WriteXMLReference(xtw, "WorkItem", "True", UrlHelper.GetTWAWorkItemBaseUrl(_workItemStore));

            XmlHelper.WriteXMLReference(xtw, "Changeset", "True", UrlHelper.GetTWAChangesetBaseUrl(_workItemStore));
            xtw.WriteEndElement();

            #endregion

            #region Write Styles
            xtw.WriteStartElement("Styles");

            colorConfiguration = new CyclicColorConfiguration("state");

            for (int i = 0; i < _allNodeTypes.Count; i++)
            {
                string label;

                label = _allNodeTypes[i];

                XmlHelper.WriteXMLNodeStyle(xtw, label, colorConfiguration.GetColor(label));
            }

            xtw.WriteEndElement();
            #endregion

            //Close the DGML document
            xtw.WriteEndElement();
            xtw.Close();

            //Open the DGML in Visual Studio
            System.Diagnostics.Process.Start(outputFile);
        }

        /// <summary>
        /// Loads the link types for processing
        /// </summary>
        private List<TempLinkType2> LoadLinkTypes(string project)
        {
            List<TempLinkType2> links = new List<TempLinkType2>();

            WorkItemLinkTypeCollection linkTypes = _workItemStore.Projects[project].Store.WorkItemLinkTypes;

            foreach (WorkItemLinkType t in linkTypes)
            {
                links.Add(new TempLinkType2() { Reverse = t.ForwardEnd.Name, Forward = t.ReverseEnd.Name });
            }

            links.Add(new TempLinkType2() { Forward = "Has Work Items", Reverse = "Has Changesets" });
            links.Add(new TempLinkType2() { Forward = "Test Results", Reverse = "Test Results" });
            links.Add(new TempLinkType2() { Forward = "In Changeset", Reverse = "Has Files" });

            //Used for dependency analysis only
            links.Add(new TempLinkType2() { Forward = "Associated Work Items", Reverse = "Has Files" });
            return links;
        }

        /// <summary>
        /// Checks to see if the work item stub exists
        /// </summary>
        /// <param name="id">The ID of the work item to find</param>
        /// <returns>The work item stub or null if it isn't found</returns>
        private WorkItemStub GetStub(int id)
        {
            WorkItemStub s;
            _workItemStubs.TryGetValue(id, out s);

            return s;
        }

        /// <summary>
        /// Gets the stub associated with the work item if it exists, otherwise a new one is
        /// created, added to the collection and returned
        /// </summary>
        /// <param name="wi">The Work Item to check</param>
        /// <returns>The associated WorkItemStub</returns>
        private WorkItemStub CheckStub(WorkItem wi)
        {
            //Perform a find on the ID, if it is found, s is populated
            WorkItemStub s = GetStub(wi.Id);

            //If it isn't found, create a new work item stub
            if (s == null)
            {
                s = new WorkItemStub();
                s.ID = wi.Id;
                s.Title = wi.Fields[CoreField.Title].Value.ToString();
                s.WorkItemTypeName = wi.Fields[CoreField.WorkItemType].Value.ToString();

                if (!_allNodeTypes.Contains(s.WorkItemTypeName))
                    _allNodeTypes.Add(s.WorkItemTypeName);

                // if we are grouping by, then add the distinct value if it's not there already
                if (String.IsNullOrEmpty(_groupByFieldReferenceName) == false)
                {
                    s.GroupyByFieldValue = wi.Fields[_groupByFieldReferenceName].Value.ToString();

                    if (!_groupByDistinctValues.Contains(s.GroupyByFieldValue))
                        _groupByDistinctValues.Add(s.GroupyByFieldValue);
                }


                _workItemStubs.Add(s.ID, s);
            }

            return s;
        }

        /// <summary>
        /// The work item to process
        /// </summary>
        /// <param name="wi"></param>
        private void ProcessWorkItemCS(WorkItem wi)
        {
            //Get a reference to or add the wi to the workitemstub list
            WorkItemStub s = CheckStub(wi);

            //Loop through the work items linked to this work item
            for (int i = 0; i < wi.WorkItemLinks.Count; i++)
            {
                if (_full || IsLinkSelected(wi.WorkItemLinks[i].LinkTypeEnd.Name))
                {
                    ProcessLinks(wi, wi.WorkItemLinks[i], s);
                }
            }

            if (_full || IsLinkSelected("Changesets"))
            {
                GetChangesets(wi, s);
            }

            if (s.WorkItemTypeName == "Test Case")
            {
                if (_full || IsLinkSelected("Test Results"))
                {
                    GetTestResults(s, wi.Project.Name);
                }
            }
        }

        /// <summary>
        /// Processes a relationship between two work items
        /// </summary>
        /// <param name="wi">The source work item</param>
        /// <param name="wiLink">The link to the target work item</param>
        /// <param name="s">The work item stub that corrosponds to the source work item</param>
        private void ProcessLinks(WorkItem wi, WorkItemLink wiLink, WorkItemStub s)
        {
            int targetID = wiLink.TargetId;
            //Check to see if the work item that is related to this one is in the list of work items and if not, add it by
            //calling this method recursively. If it is in the list of work items then we've already
            //seen this work item and we just have to figure out how it is linked to this work item
            WorkItemStub result;

            _workItemStubs.TryGetValue(targetID, out result);

            //If the work item hasn't been processed yet, process it
            if (result == null)
                ProcessWorkItemCS(_workItemStore.GetWorkItem(targetID));

            //Check to see if the ID and relationship match (we can have more than one relationship between two work items
            //for example, we could have a parent/child relationship and also a predecessor/successor relationship
            string findId;
            findId = targetID.ToString() + wiLink.LinkTypeEnd.Name;
            WorkItemRelationship result1 = s.Related.Find(stub => { return stub.ToString() == findId; });




            if (result1 == null)
            {
                WorkItemStub workItemStub;
                bool found = false;
                //Before we add this relationship in, make sure we have never added this relationship anywhere at all
                //so we make sure each relationship (forward/reverse end of the same relationship) is unique in the
                //entire result set.                

                if (_workItemStubs.TryGetValue(targetID, out workItemStub))
                {
                    for (int k = 0; k < workItemStub.Related.Count; k++)
                    {
                        if (workItemStub.Related[k].ID == wi.Id)
                        {
                            found = true;
                            break;
                        }
                    }
                }

                //If we didn't find an existing relationship, make sure we add it to the 
                //work item at the reverse end of the relationship. For example, in a Parent/
                //Child relationship the Child is the Forward end of the relationship and
                //Parent is the reverse end of the relationship. We need to add this relationship
                //to the Parent to say that the parent has children so when the links are
                //written out to the DGML they point the right direction
                if (!found)
                {
                    //get the relationship
                    TempLinkType2 t = GetRelationship(wiLink.LinkTypeEnd.Name);
                    //Determine if this is the forward or reverse end of the relationship
                    if (t.Reverse == wiLink.LinkTypeEnd.Name)
                    {
                        //This is the reverse end of the relationship so add it, otherwise skip it altogether
                        s.Related.Add(new WorkItemRelationship() { ID = targetID, End = wiLink.LinkTypeEnd.Name, Relationship = GetRelationship(wiLink.LinkTypeEnd.Name) });
                    }
                }
            }
        }

        #endregion

        /// <summary>
        /// This method takes a work item and examines it's relationships to determine what
        /// the root work item is. The use of this method is in dependency analysis which
        /// takes the following ReqA --> TaskB --> ChangesetC and changes it to 
        /// ReqA --> Changeset C. This base work item has changesets otherwise we would
        /// not be processing it. For our purposes we would be starting with TaskB and
        /// looking for ReqA or some other parent
        /// </summary>
        /// <param name="wi">The work item to discover the parent of</param>
        private void GetWorkItemParent(WorkItemStub stub)
        {
            //The rules for discovery are as follows:
            //1. Walk the parent/child relationship from the reverse to the forward node
            //2. Check the related work item ONLY when going from a Bug to a Test Case
            //   - never the reverse or you get trapped in a recursive loop
            //3. Check the affected by/affects from the reverse to the forward (in other
            //   words the standard relationship is a Requiremenet is Affected By a
            //   Change Request which has an associated changeset or child task

            WorkItemStub s = null;  //stores the related work item stubs
            bool found = false;     //indicates if we found a valid link to persue

            //Because of how the relationships are constructed, only one end of the relationship has
            //the actual link to the relationship. For example, Story1 has relationships with Task1
            //and Task2 but Task1 and Task2 don't know anything about Story1. So, when we are going
            //through this list, we have to determine which other work items are related to the work
            //item we're looking at right now.

            //NOTE I may have to come back to this because it may be a fallacy that just because
            //story1 is related to task1 and task2 doesn't mean that there isn't a "story3" that
            //is the parent of Story1
            //This returns a list of parent work items that are related to thsi work item.
            List<WorkItemStub> r = GetRelatedWorkItems(stub);
            for (int i = 0; i < r.Count; i++)
            {
                s = GetStub(r[i].ID);
                GetWorkItemParent(s);
                found = true;
            }

            //If we haven't found any other links that are parent/child then this
            //must be the root that we are associating the changesets with
            if (!found)
            {
                if (_tempStub != stub)
                {
                    //Associate the changeset with this work item
                    for (int k = 0; k < _tempStub.Changesets.Count; k++)
                    {
                        stub.Changesets.Add(_tempStub.Changesets[k]);
                    }
                }
            }
        }

        /// <summary>
        /// Gets all work item stubs related to a given work item stub
        /// </summary>
        /// <param name="stub">The work item to get relationships for</param>
        /// <returns>A list of related work item stubs</returns>
        private List<WorkItemStub> GetRelatedWorkItems(WorkItemStub stub)
        {
            List<WorkItemStub> related = new List<WorkItemStub>();

            for (int i = 0; i < _workItemStubs.Count; i++)
            {
                for (int k = 0; k < _workItemStubs[i].Related.Count; k++)
                {
                    if ((_workItemStubs[i].Related[k].ID == stub.ID) && (_workItemStubs[i].Related[k].End == "Child"))
                    {
                        related.Add(_workItemStubs[i]);
                    }
                }
            }
            return related;
        }

        /// <summary>
        /// Determines if the link for processing is selected
        /// </summary>
        /// <param name="link">The link end name</param>
        /// <returns>True if the link should be processed, otherwise false</returns>
        private bool IsLinkSelected(string link)
        {
            return GetLinkType(link).Selected;
        }

        /// <summary>
        /// Gets a link type from the list of link types
        /// </summary>
        /// <param name="link">The name of the link end</param>
        /// <returns>The link type</returns>
        private TempLinkType GetLinkType(string link)
        {
            return _selectedLinks[link];







        }

        /// <summary>
        /// Retrieves changesets associated with a work item
        /// 
        /// This call only needs to be executed once per work item id.
        /// 
        /// If multiple calls are made we ignore all calls after the first one
        /// </summary>
        /// <param name="wi">The work item to retrieve changesets for</param>
        /// <param name="stub">The work item stub to add them to</param>
        private void GetChangesets(WorkItem wi, WorkItemStub stub)
        {
            if (_visitedChangeSetsForWorkItem.ContainsKey(wi.Id)) return;

            _visitedChangeSetsForWorkItem.Add(wi.Id, null);

            //Loop through all of the links in the work item. Note that this is the links
            //not the work item links so this includes things like hyperlinks, versioned items
            //and changeset links
            for (int i = 0; i < wi.Links.Count; i++)
            {
                //Determine if this is an external link which is an indication of a changeset link
                if (wi.Links[i].BaseType == BaseLinkType.ExternalLink)
                {
                    int cs_id;
                    ExternalLink ex = (ExternalLink)wi.Links[i];

                    //We have a valid changeset link type
                    if (ex.ArtifactLinkType.Equals(_workItemStore.RegisteredLinkTypes[ArtifactLinkIds.Changeset]))
                    {
                        //Get the ID of the changeset
                        ArtifactId artifact = LinkingUtilities.DecodeUri(ex.LinkedArtifactUri);
                        cs_id = Convert.ToInt32(artifact.ToolSpecificId);

                        //It is a changeset, validate that we haven't processed it already
                        ChangesetStub c = stub.Changesets.Find(
                               delegate(ChangesetStub s)
                               {
                                   return s.ID == cs_id;
                               }
                           );

                        //It wasn't found, process it
                        if (c == null)
                        {
                            //It is a changeset so get the specific changeset
                            Changeset cs = _vcs.GetChangeset(cs_id);
                            ChangesetStub csStub = new ChangesetStub();
                            csStub.ID = cs_id;

                            //Loop through the files in the changeset which is represented by the Changes
                            for (int j = 0; j < cs.Changes.Count(); j++)
                            {
                                //Add the files to the changeset stub
                                FileChangeInfo fc = new FileChangeInfo();
                                fc.ChangeType = cs.Changes[j].ChangeType.ToString();
                                fc.FullPath = cs.Changes[j].Item.ServerItem;
                                csStub.Files.Add(fc);

                                //Check to see if we have added this file to the master list,
                                //if we haven't, add it
                                if (_allFiles.ContainsKey(fc.FileName) == false)
                                    _allFiles.Add(fc.FileName, fc);
                            }
                            stub.Changesets.Add(csStub);

                            //Loop through the files in the changeset which is represented by the Changes
                            for (int j = 0; j < cs.Changes.Count(); j++)
                            {
                                ChangesetFilesToWorkItems(cs.Changes[j].Item.ServerItem);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the entire history of a single file.
        /// 
        /// This code only needs to run once per file (since it analyses it's entire history).
        /// 
        /// Therefore if we have been already called once for a given filepath we simple return avoiding
        /// duplicate processing
        /// </summary>
        /// <param name="serverpath">The path to the item on the server who's history to retrieve</param>
        private void ChangesetFilesToWorkItems(string serverpath)
        {
            if (_visitedServerPath.ContainsKey(serverpath)) return;

            _visitedServerPath.Add(serverpath, null);

            //This syntax retrieves the entire history of a file - but not across branches
            //as that typically won't have as big an impact on changes
            System.Collections.IEnumerable history = _vcs.QueryHistory(serverpath, VersionSpec.Latest,
                0, RecursionType.OneLevel, null, null, null, Int32.MaxValue, true, false);

            //At this point history contains every changeset the given file has been involved in
            //Next we need to get all of the work items associated with that changeset and hence that file
            //and add all items to the drawing surface (eventually).
            foreach (Changeset cs in history)
            {
                WorkItem[] wi = cs.WorkItems;
                for (int i = 0; i < wi.Length; i++)
                {
                    ProcessWorkItemCS(wi[i]);
                }
            }
        }

        /// <summary>
        /// Get the results associated with a test case
        /// </summary>
        /// <param name="stub">The work item to get the results for</param>
        private void GetTestResults(WorkItemStub stub, string project)
        {
            //Return all of the test results in which this test case was executed
            string query = string.Format("SELECT * FROM TestResult WHERE TestCaseId = {0}", stub.ID);
            int planId = 0;

            //Get the list of results for the test case
            foreach (ITestCaseResult tcr in _testManagementTeamProject.TestResults.Query(query))
            {
                TestPlanInfo plan;

                #region Get the Test Plan
                ITestRun tr = tcr.GetTestRun();

                planId = tr.TestPlanId;














                //Is this part of a test plan that we already have? If not, add it
                if (_testPlans.TryGetValue(planId, out plan) == false)
                {
                    string planQuery = string.Format("SELECT * FROM TestPlan WHERE PlanId = {0}", planId);
                    foreach (ITestPlan testPlan in _testManagementTeamProject.TestPlans.Query(planQuery))
                    {
                        plan = new TestPlanInfo() { TestPlanID = planId, TestPlanName = testPlan.Name };
                        _testPlans.Add(planId, plan);
                        break;
                    }
                }
                #endregion

                //Check to see if we've added the test run already
                bool found = false;
                for (int k = 0; k < plan.TestRuns.Count; k++)
                {
                    if (plan.TestRuns[k].ID == tcr.TestRunId)
                    {
                        found = true;
                    }
                }

                //If not, add it
                if (!found)
                    plan.TestRuns.Add(new TestRunStub() { ID = tcr.TestRunId, Url = UrlHelper.GetMtmTestRunUrl(_workItemStore, tcr.TestRunId, project) });

                //add the results
                TestResultStub resultStub = new TestResultStub();
                resultStub.TestPlan = plan;
                resultStub.TestCaseID = tcr.TestCaseId;
                resultStub.TestResultID = tcr.TestResultId;
                resultStub.TestRunID = tcr.TestRunId;
                resultStub.Outcome = tcr.Outcome;
                resultStub.Url = UrlHelper.GetMtmTestResultsUrl(_workItemStore, tcr.TestRunId, tcr.TestResultId, project);
                stub.TestResults.Add(resultStub);
            }
        }
    }
}